#include "lib/drivers/pvmdriver_pulse.h"
#include "lib/drivers/pvmdeviceinterface.h"

#include "lib/pvmerrors.h"
#include "lib/pvmstrings.h"
#include "lib/pvmdriver.h"

#include <pulse/pulseaudio.h>
#include <string.h>
#include <unistd.h>
#include "stderrorlog.h"

#include "lib/memory/pvmmemory.h"

#ifdef _DEBUG
	#define DLOG(x, args...) stderrorlog(x, args)
#else
	#define DLOG(x, args...) 
#endif



#define PVMD_PULSE_WAIT_FOR_SUSPEND		1000
#define PVMD_PULSE_DEFAULT_BUFFER_LENGTH	240
#define PVMD_PULSE_DEVICE_TYPE	(EPVM_DTYPE_OUTPUT | EPVM_DTYPE_DEVICE)
#define NUM_BUFFERS 4

static void pa_state_cb(pa_context* c, void* userdata);
static void stream_request_cb(pa_stream *s, size_t length, void *userdata);
static void stream_state_cb(pa_stream *p, void *userdata);
static void stream_underflow_cb(pa_stream *p, void *userdata);

static const int pa_latency = (PVMD_PULSE_DEFAULT_BUFFER_LENGTH / NUM_BUFFERS) * 1000; // start latency in micro seconds


class PVMDevicePulse : public IPVMDeviceOutSimple
{
public:
	friend void stream_request_cb(pa_stream*, size_t, void*);

	PVMDevicePulse(PVMDriverPulse* driver);
	~PVMDevicePulse();

	virtual pvm_uint32 GetDeviceType();
	virtual void DeviceClose();
	virtual pvm_bool DeviceOpened();

	virtual pvm_bool SetOption(const pvm_char* optionName, void* param, pvm_uint32 paramSize);
	virtual pvm_bool GetOption(const pvm_char* optionName, void* param, pvm_uint32* paramSize);

	virtual void Suspend();
	virtual pvm_bool IsSuspended();
	virtual void Resume();

	virtual void DeviceNotify();

	virtual void SetBufferLengthMs(pvm_uint32 bufferSize);
	virtual pvm_uint32 GetBufferRealLengthMs();

	virtual pvm_int32 GetCPUUsage();

	virtual pvm_uint32 GetChanelsNum();
	virtual pvm_uint32 GetBitsPerSample();
	virtual pvm_uint32 GetSamplesPerSecond();

	pvm_bool Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended);

	virtual IPVMDriverBase* GetDriver() {return driver;}
	
	void suspendSession();
	void resumeSession();
protected:
	void DeInit();
	void OnInitPulseError();
	void PulseDeInit();
	pvm_bool DeviceHasMoreData();
	void FillAndWrite(pa_stream* stream, size_t len);
protected:

	pvm_bool isDeviceInitialized;

	PVMCriticalSection pvmdGlobalVarGuard;

	pvm_bool runThread;
	pvm_bool isThreadRunning;

	pvm_bool pauseRendering;
	pvm_bool isRenderingPaused;

	//wave out stuff
	pvm_uint32			currentSampleRate;
	pvm_uint32			currentbitsPerSample;
	pvm_uint32			currentbytesPerSample;
	pvm_uint32			currentChanelsNum;

	pvm_int	prevSamplesRendered;

	//rendering sources
	pvm_uint32 currentBufferLength;
	pvm_uint32 newBufferLength;

	int renderBufferSize;
	int renderBufferSizeInSamples;
	pvm_int8* renderBuffer[NUM_BUFFERS];

	PVMThreadHandle hPlayThread;
	PVMEventHandle hPlayEvent;
	static PVMThreadRetVal PVMCALL PlaybackThreadProc(void* lpParameter);

	void PlaybackLoop();
	bool ResizeRenderBuffer(int newLengthMs);

	IPVMDeviceOutSimpleWaveSource *waveSource;

	// Pulse Audio stuff
	pa_mainloop* pa_ml;
	pa_mainloop_api* pa_mlapi;
	pa_context* pa_ctx;
	pa_stream* playstream;
	pa_sample_spec ss;
	pa_buffer_attr bufattr;

	PVMDriverPulse* driver;
};

pvm_uint32 PVMDevicePulse::GetChanelsNum()
{
	return currentChanelsNum;
}

pvm_uint32 PVMDevicePulse::GetBitsPerSample()
{
	return currentbitsPerSample;
}

pvm_uint32 PVMDevicePulse::GetSamplesPerSecond()
{
	return currentSampleRate;
}

pvm_int32 PVMDevicePulse::GetCPUUsage()
{
	return -1;
}

PVMDevicePulse::PVMDevicePulse(PVMDriverPulse* driver)
{
	DLOG("PVMDevicePulse::PVMDevicePulse -->\n", 0);
	isDeviceInitialized = false;

	//threading vars
	PVMInitThreadVar(&hPlayThread);

	runThread = false;
	isThreadRunning = false;

	pauseRendering = false;
	isRenderingPaused = false;

	currentSampleRate = 0;
	currentbitsPerSample = 0;
	currentbytesPerSample = 0;
	currentChanelsNum = 0;

	currentBufferLength = 0;
	newBufferLength = PVMD_PULSE_DEFAULT_BUFFER_LENGTH;

	renderBufferSize = 0;
	renderBufferSizeInSamples = 0;

	for (int i = NUM_BUFFERS; --i >= 0;)
	{
		renderBuffer[i] = 0;
	}

	waveSource = NULL;
	this->driver = driver;
	DLOG("PVMDevicePulse::PVMDevicePulse <--\n", 0);
}

PVMDevicePulse::~PVMDevicePulse()
{
	DeInit();
}

pvm_bool PVMDevicePulse::Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 /*deviceNum*/, void* /*additionalInfo*/, pvm_uint32 /*infoSize*/, IPVMDeviceOutSimpleWaveSource* waveSource, pvm_bool deviceSuspended)
{
	DLOG("PVMDevicePulse::Init sps: %d, bps: %d, ch: %d\n", samplesPerSec, bitsPerSample, chanelsNum);
	PVMInitCritical(&pvmdGlobalVarGuard);
	PVMEnterCritical(&pvmdGlobalVarGuard);
	
	isDeviceInitialized = false;
	runThread = true;
	isThreadRunning = false;

	pauseRendering = deviceSuspended;
	isRenderingPaused = false;

	PVMInitEvent(&hPlayEvent);

	PVMInitThreadVar(&hPlayThread);
	if(!PVMCreateThread(&hPlayThread, PVMDevicePulse::PlaybackThreadProc, (void*)this))
	{
		PVMLeaveCritical(&pvmdGlobalVarGuard);
		PVMDeinitCritical(&pvmdGlobalVarGuard);
		PVMDeinitEvent(&hPlayEvent);
		return false;
	}

	PVMLeaveCritical(&pvmdGlobalVarGuard);

	if(!PVMIsThreadValid(hPlayThread))
	{
		PVMDeinitCritical(&pvmdGlobalVarGuard);
		PVMDeinitEvent(&hPlayEvent);
		return false;
	}

	PVMEnterCritical(&pvmdGlobalVarGuard);
	currentSampleRate = samplesPerSec;
	currentbitsPerSample = bitsPerSample;
	currentbytesPerSample = bitsPerSample/8;
	currentChanelsNum = chanelsNum;	
	
	
	// TODO open device
	// Create a mainloop API and connection to the default server
	int pa_ready = 0;
	pa_ml = pa_mainloop_new();
	pa_mlapi = pa_mainloop_get_api(pa_ml);
	pa_ctx = pa_context_new(pa_mlapi, "PVM Application");
	pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);

	// This function defines a callback so the server will tell us it's state.
	// Our callback will wait for the state to be ready.  The callback will
	// modify the variable to 1 so we know when we have a connection and it's
	// ready.
	// If there's an error, the callback will set pa_ready to 2
	pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);

	// We can't do anything until PA is ready, so just iterate the mainloop
	// and continue
	while (pa_ready == 0)
	{
		pa_mainloop_iterate(pa_ml, 1, NULL);
	}
	if (pa_ready == 2)
	{
		DLOG("pa_context_connect - failed\n", 0);
		PVMLeaveCritical(&pvmdGlobalVarGuard);
		OnInitPulseError();
		return false;
	}
	DLOG("pa_context_connect - success\n", 0);

	pa_proplist* proplist = pa_proplist_new();
	if (!proplist)
	{
		DLOG("pa_proplist_new - failed\n", 0);
		PVMLeaveCritical(&pvmdGlobalVarGuard);
		OnInitPulseError();
		return false;
	}
	pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "x-maemo");

	ss.rate = currentSampleRate;
	ss.channels = currentChanelsNum;
	ss.format = PA_SAMPLE_S16LE;
	playstream = pa_stream_new_with_proplist(pa_ctx, "HC Game playback", &ss, NULL, proplist);
	if (!playstream)
	{
		DLOG("pa_stream_new_with_proplist - failed\n", 0);
		PVMLeaveCritical(&pvmdGlobalVarGuard);
		OnInitPulseError();
		return false;
	}
	DLOG("pa_stream_new_with_proplist - success\n", 0);

	pa_stream_set_write_callback(playstream, stream_request_cb, (void*)this);
	pa_stream_set_underflow_callback(playstream, stream_underflow_cb, (void*)"underflow");
	pa_stream_set_overflow_callback(playstream, stream_underflow_cb, (void*)"overflow");

	pa_cvolume volume_control;
	volume_control.channels = currentChanelsNum;
	for (unsigned int i = 0; i < PA_CHANNELS_MAX; ++i)
	{
		volume_control.values[i] = PA_VOLUME_MUTED;
	}
	pa_cvolume_set(&volume_control, currentChanelsNum, (pa_volume_t)(PA_VOLUME_NORM * 0.75f));


	bufattr.fragsize = (uint32_t)-1; // used in recording
	bufattr.maxlength = pa_usec_to_bytes(pa_latency, &ss);
	bufattr.minreq = pa_usec_to_bytes(0, &ss);
	bufattr.prebuf = (uint32_t)-1;
	bufattr.tlength = pa_usec_to_bytes(pa_latency, &ss);
	pa_stream_flags sflags = (pa_stream_flags)(
				PA_STREAM_INTERPOLATE_TIMING
				|PA_STREAM_ADJUST_LATENCY
				|PA_STREAM_AUTO_TIMING_UPDATE
				);

	pa_ready = 0;
	pa_stream_set_state_callback(playstream, stream_state_cb, (void*)&pa_ready);
	int r = pa_stream_connect_playback(playstream, NULL, &bufattr,
								sflags, &volume_control, NULL);
     if (r < 0)
	 {
		 DLOG("pa_stream_connect_playback - failed\n", 0);
		 PVMLeaveCritical(&pvmdGlobalVarGuard);
		 OnInitPulseError();
		 return false;
	 }
	 DLOG("pa_stream_connect_playback - success\n", 0);

	 // Wait for stream ready state
//	 while (pa_ready == 0)
//	 {
//		 pa_mainloop_iterate(pa_ml, 1, NULL);
//	 }
//	 if (pa_ready == 2)
//	 {
//		 PVMLeaveCritical(&pvmdGlobalVarGuard);
//		 OnInitPulseError();
//		 return false;
//	 }


	this->waveSource = waveSource;
	isDeviceInitialized = true;


	// Set volume
//	struct myblock
//	{
//	   static void op_success_cb (pa_context* /*c*/, int success, void* userdata)
//	   {
//		   int* ready = (int*)userdata;
//		   *ready = (success? 1: 2);
//	   }
//	};
//	pa_ready = 0;
//	uint32_t sink_index = pa_stream_get_device_index(playstream);
//	pa_operation* op = pa_context_set_sink_volume_by_index(pa_ctx, sink_index, &volume_control, myblock::op_success_cb, (void*)&pa_ready);
//	while (pa_ready == 0)
//	{
//		pa_mainloop_iterate(pa_ml, 1, NULL);
//	}
//	pa_operation_unref(op);
//	if (pa_ready == 2)
//	{
//		PVMLeaveCritical(&pvmdGlobalVarGuard);
//		OnInitPulseError();
//		return false;
//	}


	PVMLeaveCritical(&pvmdGlobalVarGuard);
	DeviceNotify();

	return true;
}

void PVMDevicePulse::OnInitPulseError()
{
	DLOG("PVMDevicePulse::OnInitPulseError -->\n", 0);
	if(PVMIsThreadValid(hPlayThread))
	{
		runThread  = false;
		if(isRenderingPaused)
			Resume();
		while(isThreadRunning);
	}

	PVMDeinitCritical(&pvmdGlobalVarGuard);
	PVMDeinitEvent(&hPlayEvent);
	
	PulseDeInit();
	DLOG("PVMDevicePulse::OnInitPulseError <--\n", 0);
}

void PVMDevicePulse::PulseDeInit()
{
	DLOG("PVMDevicePulse::PulseDeInit -->\n", 0);

	// clean up and disconnect
	if (isDeviceInitialized)
	{
		pa_stream_disconnect(playstream);
	}
	pa_stream_unref(playstream);
    pa_context_disconnect(pa_ctx);
    pa_context_unref(pa_ctx);
    pa_mainloop_free(pa_ml);

	DLOG("PVMDevicePulse::PulseDeInit <--\n", 0);
}


void PVMDevicePulse::DeInit()
{
	DLOG("PVMDevicePulse::DeInit -->\n", 0);
	if(isDeviceInitialized)
	{
		PVMEnterCritical(&pvmdGlobalVarGuard);
		runThread  = false;
		PVMLeaveCritical(&pvmdGlobalVarGuard);


		if(isRenderingPaused)
			Resume();

		while(isThreadRunning);
		PVMJoinThread(hPlayThread);
	}

	PulseDeInit();

	if(isDeviceInitialized)
	{
		PVMEnterCritical(&pvmdGlobalVarGuard);
		isDeviceInitialized = false;

		PVMLeaveCritical(&pvmdGlobalVarGuard);

		PVMDeinitCritical(&pvmdGlobalVarGuard);
		PVMDeinitEvent(&hPlayEvent);
	}
	
	for (int i = NUM_BUFFERS; --i >= 0;)
	{
		delete[] renderBuffer[i]; renderBuffer[i] = NULL;		
	}

	waveSource = NULL;
	DLOG("PVMDevicePulse::DeInit <--\n", 0);
}

pvm_uint32 PVMDevicePulse::GetDeviceType()
{
	return PVMD_PULSE_DEVICE_TYPE;
}

void PVMDevicePulse::DeviceClose()
{
	DLOG("PVMDevicePulse::DeviceClose -->\n", 0);
	DeInit();
	delete this;
	DLOG("PVMDevicePulse::DeviceClose <--\n", 0);
}

pvm_bool PVMDevicePulse::DeviceOpened()
{
	return isDeviceInitialized;
}

pvm_bool PVMDevicePulse::SetOption(const pvm_char* /*optionName*/, void* /*param*/, pvm_uint32 /*paramSize*/)
{
	return false;
}

pvm_bool PVMDevicePulse::GetOption(const pvm_char* /*optionName*/, void* /*param*/, pvm_uint32* /*paramSize*/)
{
	return false;
}

void PVMDevicePulse::Suspend()
{
	DLOG("PVMDevicePulse::Suspend -->\n", 0);

	if(pauseRendering && pauseRendering)
	{
		DLOG("PVMDevicePulse::Suspend - already paused <--\n", 0);
		return;
	}
	
	PVMEnterCritical(&pvmdGlobalVarGuard);
	pauseRendering = true;
	PVMLeaveCritical(&pvmdGlobalVarGuard);

	pvm_uint32 time1 = PVMGetMilliSecondsCount();
	pvm_uint32 time2;
	if(PVMIsThreadValid(hPlayThread))
	{
		while(!isRenderingPaused)
		{
			time2 = PVMGetMilliSecondsCount();
			if(time2 - time1 > PVMD_PULSE_WAIT_FOR_SUSPEND)
				break;
			//check for long overflow - will no hang but will wait more than WAIT_FOR_SUSPEND
			if(time1 > time2)
				time1 = time2;

			PVMSleep(1);
		}
	}
	DLOG("PVMDevicePulse::Suspend <--\n", 0);

}

pvm_bool PVMDevicePulse::IsSuspended()
{
	return isRenderingPaused && pauseRendering;
}

void PVMDevicePulse::Resume()
{
	DLOG("PVMDevicePulse::Resume -->\n", 0);

	PVMEnterCritical(&pvmdGlobalVarGuard);
	pauseRendering = false;
	PVMLeaveCritical(&pvmdGlobalVarGuard);

	if(isRenderingPaused)
	{
		PVMEnterCritical(&pvmdGlobalVarGuard);
		isRenderingPaused = false;
		PVMLeaveCritical(&pvmdGlobalVarGuard);
		PVMSignalEvent(&hPlayEvent);
	}
	DLOG("PVMDevicePulse::Resume <--\n", 0);
}

void PVMDevicePulse::DeviceNotify()
{
	if(!pauseRendering && isRenderingPaused)
	{
		isRenderingPaused = false;
		if(PVMIsThreadValid(hPlayThread))
		{
			PVMSignalEvent(&hPlayEvent);
		}
	}
}

void PVMDevicePulse::SetBufferLengthMs(pvm_uint32 bufferSize)
{
	newBufferLength = bufferSize;
	DeviceNotify();
}

pvm_uint32 PVMDevicePulse::GetBufferRealLengthMs()
{
	return currentBufferLength;
}

PVMThreadRetVal PVMCALL PVMDevicePulse::PlaybackThreadProc(void* lpParameter)
{
	((PVMDevicePulse*)lpParameter)->PlaybackLoop();
	return 0;
}

PVM_FORCEINLINE pvm_bool PVMDevicePulse::DeviceHasMoreData()
{
	if(prevSamplesRendered != 0)
		return true;

	return waveSource->HasMoreData();
}

void PVMDevicePulse::PlaybackLoop()
{
	DLOG("PVMDevicePulse::PlaybackLoop -->\n", 0);

	isThreadRunning = true;
	bool needSuspendThread = false;
	prevSamplesRendered = 0;
	pvm_bool srcReady = false;
	
	while(runThread)
	{
		needSuspendThread = false;

		if(!srcReady)
		{
			if(waveSource == NULL || !waveSource->IsInitialized())
				needSuspendThread = true;
			else
				srcReady = true;
		}
		else if((pauseRendering && !isRenderingPaused) || !DeviceHasMoreData())
		{
			needSuspendThread = true;
		}

		if(needSuspendThread)
		{
			PVMEnterCritical(&pvmdGlobalVarGuard);
			isRenderingPaused = true;
			needSuspendThread = false;

			DLOG("LOOP PVM Thread sleeped\n", 0);

			PVMLeaveCritical(&pvmdGlobalVarGuard);
			PVMWaitForEvent(&hPlayEvent);
			DLOG("LOOP PVM Thread resumed\n", 0);

			// Trigger stream after awake
			// Not sure is this really needed?
			/*
			pa_operation* op = pa_stream_trigger(playstream, NULL, NULL);
			if (op)
			{
				DLOG("pa_stream_trigger started\n", 0);
				pa_operation_unref(op);
			}
			else
			{
				DLOG("pa_stream_trigger failed\n", 0);
			}
			*/
		}

		if((currentBufferLength != newBufferLength || renderBuffer[0] == NULL) &&
			waveSource != NULL && waveSource->IsInitialized())
		{
			if(!ResizeRenderBuffer(newBufferLength))
			{
				//handle error
				DLOG("LOOP Unhandled ResizeRenderBuffer error\n", 0);
			}
		}

		// See Fill and write in the FillAndWrite method

		// Iterate the main loop and go again.  The second argument is whether
	    // or not the iteration should block until something is ready to be
	    // done.  Set it to zero for non-blocking.
	    // int res =
		pa_mainloop_iterate(pa_ml, 1, NULL);
		
		/*
		// Logging state to debug
		int ss = pa_stream_get_state(playstream);
		if (pa_stream_is_suspended(playstream))
		{
			DLOG("LOOP PA stream is suspended\n", 0);
			fprintf(stderr, "LOOP stream is suspended\n");
		}
		if (pa_stream_is_corked(playstream))
		{
			DLOG("LOOP PA stream is corcked\n", 0);
			fprintf(stderr, "LOOP stream is corcked\n");
		}
		if (ss != PA_STREAM_READY)
		{
			DLOG("LOOP PA stream is not ready %d\n", (int)ss);
			fprintf(stderr, "LOOP PA stream is not ready %d\n", ss);
		}
		*/

		// Sleep
		/*
		int msecs = 2;
		struct timeval tv;
		gettimeofday(&tv, 0);
		struct timespec ti;

		ti.tv_nsec = (tv.tv_usec + (msecs % 1000) * 1000) * 1000;
		ti.tv_sec = tv.tv_sec + (msecs / 1000) + (ti.tv_nsec / 1000000000);
		ti.tv_nsec %= 1000000000;

		pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
		pthread_cond_t cnd = PTHREAD_COND_INITIALIZER;

		int errMtx = pthread_mutex_init(&mtx, 0);
		int errCnd = pthread_cond_init(&cnd, 0);

		if (errMtx == 0 && errCnd == 0)
		{
			errMtx = pthread_mutex_lock(&mtx);
			if (errMtx == 0)
			{
				pthread_cond_timedwait(&cnd, &mtx, &ti);
				pthread_mutex_unlock(&mtx);
			}
			pthread_cond_destroy(&cnd);
			pthread_mutex_destroy(&mtx);
		}
		*/
		//--

	}

	PVMEnterCritical(&pvmdGlobalVarGuard);
	isThreadRunning = false;
	pauseRendering = false;
	isRenderingPaused = false;
	PVMLeaveCritical(&pvmdGlobalVarGuard);

	DLOG("PVMDevicePulse::PlaybackLoop <--\n", 0);
}

void PVMDevicePulse::FillAndWrite(pa_stream* stream, size_t len)
{
	pa_stream_state_t sstate = pa_stream_get_state(stream);

	if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
	{
		if(waveSource!=NULL && DeviceHasMoreData())
		{
			int lenInSamples = len / currentbytesPerSample;
			prevSamplesRendered = waveSource->FillBuffer(renderBuffer[0], lenInSamples);
			pa_stream_write(stream, renderBuffer[0], len, NULL, 0LL, PA_SEEK_RELATIVE);
		}
		else
		{
			int r = pa_stream_write(stream, renderBuffer[0], len, NULL, 0LL, PA_SEEK_RELATIVE);
			DLOG("FillAndWrite but no data, len: %d, res %d\n", (int)len, r);
		}
	}
	else
	{
		DLOG("FillAndWrite PA stream is not good %d\n", (int)sstate);
	}
}

bool PVMDevicePulse::ResizeRenderBuffer(int newLengthMs)
{
	PVMEnterCritical(&pvmdGlobalVarGuard);
	int sampleRate = currentSampleRate;
	int sampleSize = currentbytesPerSample;
	int chanelsNum = currentChanelsNum;
	PVMLeaveCritical(&pvmdGlobalVarGuard);

	int renderStepSize = sampleSize*chanelsNum;
	int newRenderBufferSizeInBytes = sampleRate*renderStepSize*newLengthMs/(1000*NUM_BUFFERS);
	//buffer size must be multiple of chanelNum*sampleSize
	newRenderBufferSizeInBytes = (newRenderBufferSizeInBytes/renderStepSize)*renderStepSize;

	renderBufferSize = newRenderBufferSizeInBytes;
	renderBufferSizeInSamples = renderBufferSize/sampleSize;
	currentBufferLength = newLengthMs;
	for (int i = NUM_BUFFERS; --i >= 0;)
	{
		delete[] renderBuffer[i];
		renderBuffer[i] = new pvm_int8[renderBufferSize];
		memset(renderBuffer[i], 0, renderBufferSize);
	}

	if(waveSource!=NULL)
		waveSource->ResizeRenderBuffer(renderBufferSize);

	return true;
}

////////////////////////////////////////////////////////////////////////////////
// PVMDriverPulse
////////////////////////////////////////////////////////////////////////////////

PVMDriverPulse::PVMDriverPulse()
{
}

PVMDriverPulse::~PVMDriverPulse()
{
}

pvm_bool PVMDriverPulse::Init()
{

	return true;
}

pvm_bool PVMDriverPulse::IsInitialized()
{
	return true;
}

void PVMDriverPulse::DriverClose()
{
	delete this;
}

pvm_uint32 PVMDriverPulse::GetDriverType()
{
	return PVMD_PULSE_DEVICE_TYPE;
}

pvm_bool PVMDriverPulse::GetDriverOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32* /*optionSize*/)
{
	return false;
}

pvm_bool PVMDriverPulse::SetDriverOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32 /*optionSize*/)
{
	return false;
}


pvm_uint32 PVMDriverPulse::DevicesNum()
{
	return 1;
}

pvm_int32 PVMDriverPulse::DeviceName(pvm_int32 /*nDevice*/, pvm_char* /*buffer*/, pvm_uint32 /*maxLength*/)
{
	return -1;
}

pvm_bool PVMDriverPulse::DeviceEnumModes(pvm_int32 /*nDeivce*/, TFormatEnumCallbackProc enumProc, void* /*param*/)
{
	if(!enumProc)
	{
		return false;
	}

	// Define our pulse audio loop and connection variables
    pa_mainloop* pa_ml;
    pa_mainloop_api* pa_mlapi;
    pa_operation* pa_op = 0;
    pa_context* pa_ctx;

    // We'll need these state variables to keep track of our requests
    int state = 0;
    int pa_ready = 0;

    // Create a mainloop API and connection to the default server
    pa_ml = pa_mainloop_new();
    pa_mlapi = pa_mainloop_get_api(pa_ml);
    pa_ctx = pa_context_new(pa_mlapi, "pvm_enum");

    // This function connects to the pulse server
    pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);

	struct myblock
	{
		static void pa_sinklist_cb(pa_context* /*c*/, const pa_sink_info* /*l*/, int eol, void */*userdata*/)
		{
			// int ctr = 0;

		    // If eol is set to a positive number, you're at the end of the list
		    if (eol > 0)
			{
		        return;
		    }
		}
	};

	// This function defines a callback so the server will tell us it's state.
    // Our callback will wait for the state to be ready.  The callback will
    // modify the variable to 1 so we know when we have a connection and it's
    // ready.
    // If there's an error, the callback will set pa_ready to 2
    pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);

	for(;;)
	{
		// We can't do anything until PA is ready, so just iterate the mainloop
        // and continue
        if (pa_ready == 0) {
            pa_mainloop_iterate(pa_ml, 1, NULL);
            continue;
        }
        // We couldn't get a connection to the server, so exit out
        if (pa_ready == 2)
		{
            pa_context_disconnect(pa_ctx);
            pa_context_unref(pa_ctx);
            pa_mainloop_free(pa_ml);
            break;
        }

		if (state == 0)
		{
			// This sends an operation to the server.  pa_sinklist_info is
			// our callback function and a pointer to our devicelist will
			// be passed to the callback The operation ID is stored in the
			// pa_op variable
			pa_op = pa_context_get_sink_info_list(pa_ctx,
					myblock::pa_sinklist_cb,
					0
					);

			// Update state for next iteration through the loop
			state++;
		}
		else
		{
			// Now we wait for our operation to complete.  When it's
			// complete our device list is filled out
			if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE)
			{
				pa_operation_unref(pa_op);
				pa_context_disconnect(pa_ctx);
				pa_context_unref(pa_ctx);
				pa_mainloop_free(pa_ml);
				break;
			}

		}

		// Iterate the main loop and go again.  The second argument is whether
        // or not the iteration should block until something is ready to be
        // done.  Set it to zero for non-blocking.
        pa_mainloop_iterate(pa_ml, 1, NULL);
	}


//	enumProc(1, 8, 11025, param);
//	enumProc(1, 8, 22050, param);
//	enumProc(1, 8, 44100, param);
//	enumProc(1, 8, 48000, param);

//	enumProc(1, 16, 11025, param);
//	enumProc(1, 16, 22050, param);
//	enumProc(1, 16, 44100, param);
//	enumProc(1, 16, 48000, param);

//	enumProc(2, 8, 11025, param);
//	enumProc(2, 8, 22050, param);
//	enumProc(2, 8, 44100, param);
//	enumProc(2, 8, 48000, param);

//	enumProc(2, 16, 11025, param);
//	enumProc(2, 16, 22050, param);
//	enumProc(2, 16, 44100, param);
//	enumProc(2, 16, 48000, param);

	return true;
}

IPVMDeviceOutSimple* PVMDriverPulse::DeviceOpen(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended)
{
	if(!IsInitialized())
		return NULL;

	PVMDevicePulse* device = new PVMDevicePulse(this);
	if(!device)
		return NULL;
	if(!device->Init(samplesPerSec, bitsPerSample, chanelsNum, deviceNum, additionalInfo, infoSize, waveSource, deviceSuspended))
	{
		device->DeviceClose();
		return NULL;
	}

	return device;
}

void PVMDriverPulse::CloseDevice(IPVMDeviceBase* device)
{
	if(device!=NULL)
		device->DeviceClose();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

IPVMDriverOut* PVMDriverCreatePulse()
{
	PVMDriverPulse* outDrv = new PVMDriverPulse();
	if(!outDrv)
		return NULL;
	PVMDriverSimple* simpledrv = new PVMDriverSimple(outDrv);
	if(!simpledrv)
	{
		outDrv->DriverClose();
	}

	return simpledrv;
}

static pvm_int32 DriverNameShort(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("Pulse software"), buffer, maxLength);
}

static pvm_int32 DriverNameFull(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("Pulse software mixing"), buffer, maxLength);
}

static pvm_uint32 DriverTypeAndFlags()
{
	return PVMD_PULSE_DEVICE_TYPE;
}

static IPVMDriverBase* DriverLoad()
{
	return PVMDriverCreatePulse();
}

IPVMDriverDesc	pulseDirectDesc;

IPVMDriverDesc* PVMGetDriverDescPulse()
{
	pulseDirectDesc.DriverNameShort = DriverNameShort;
	pulseDirectDesc.DriverNameFull = DriverNameFull;
	pulseDirectDesc.DriverTypeAndFlags = DriverTypeAndFlags;
	pulseDirectDesc.DriverLoad = DriverLoad;
	return &pulseDirectDesc;
}

//-----------------------------------------------------------------------------
// Pulse Audio
//-----------------------------------------------------------------------------
static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
{
	PVMDevicePulse* device = static_cast<PVMDevicePulse*>(userdata);
	device->FillAndWrite(s, length);
}

void stream_underflow_cb(pa_stream* /*s*/, void *userdata)
{
	const char* flow = (const char*)userdata;
	DLOG("PA stream is %s\n", flow);
}

void pa_state_cb(pa_context* c, void* userdata)
{
	pa_context_state_t state;
    int* pa_ready = (int*)userdata;

    state = pa_context_get_state(c);
    switch  (state)
	{
            // There are just here for reference
            case PA_CONTEXT_UNCONNECTED:
            case PA_CONTEXT_CONNECTING:
            case PA_CONTEXT_AUTHORIZING:
            case PA_CONTEXT_SETTING_NAME:
            default:
                    break;
            case PA_CONTEXT_FAILED:
            case PA_CONTEXT_TERMINATED:
                    *pa_ready = 2;
					DLOG("Context failed or terminated %d\n", (int)state);
                    break;
            case PA_CONTEXT_READY:
                    *pa_ready = 1;
					DLOG("Context READY\n", 0);
                    break;
    }
}

void stream_state_cb(pa_stream* s, void* userdata)
{
	int* ready = (int*)userdata;
	pa_stream_state_t sstate = pa_stream_get_state(s);
	switch (sstate)
	{
	case PA_STREAM_READY:
		DLOG("PA stream state: READY\n", 0);
		*ready = 1;
		break;
	case PA_STREAM_TERMINATED:
		DLOG("PA stream state: TERMINATED\n", 0);
		*ready = 2;
		break;
	case PA_STREAM_FAILED:
		DLOG("PA stream state: FAILED\n", 0);
		*ready = 2;
		break;
	case PA_STREAM_UNCONNECTED:
		DLOG("PA stream state: UNCONNECTED\n", 0);
		*ready = 2;
		break;
	case PA_STREAM_CREATING:
		DLOG("PA stream state: CREATING\n", 0);
		*ready = 0; // keep waiting
		break;
	default:
		DLOG("PA stream state UNKNOWN: %d\n", sstate);
		break;
	}
}

#undef	PVMD_PULSE_WAIT_FOR_SUSPEND
#undef	PVMD_PULSE_DEFAULT_BUFFER_LENGTH
#undef	PVMD_PULSE_DEVICE_TYPE
